﻿using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using LINQPad.Extensibility.DataContext;
using LINQPad.OpenAccess.Helpers;
using LINQPad.OpenAccess.Settings;

namespace LINQPad.OpenAccess.UI.ViewModel
{
    /// <summary>
    /// ConnectionSettingsHost - ViewModel
    /// </summary>
    public class ConnectionSettingsHostViewModel : DriverSubSettingsViewModelBase<ConnectionSettingsHostViewModel>, IPersistableSettingsViewModel<DriverSettings>
    {
        #region Private

        #region - Vars

        private readonly IConnectionSettingsViewModel[] _allConnectionSettings;
        private IConnectionSettingsViewModel[] _availableConnectionSettings;

        private IConnectionSettingsViewModel _connectionSettings;
        private string _providerName;
        private IConnectionInfo _connectionInfo;
        private DriverSettings _driverSettings;

        #endregion

        #region - Functions

        /// <summary>
        /// Sets the provider name.
        /// </summary>
        /// <param name="providerName">Name of the provider.</param>
        /// <param name="autoUpdateConnectionSettings">set to <c>true</c> if the connection settings should be automatically updated.</param>
        private void SetProviderName( string providerName, bool autoUpdateConnectionSettings = true )
        {
            if ( this._providerName == providerName )
                return;

            this._providerName = providerName;
            this.OnPropertyChanged( ( ) => this.ProviderName );
            this.UpdateAvailableConnectionSettings( );

            if ( autoUpdateConnectionSettings )
                this.UpdateConnectionSettingsForProviderName( );

            this.Invalidate( );
        }
        /// <summary>
        /// Syncs the provider name to the connection settings.
        /// </summary>
        private void SyncProviderNameToConnectionSettings( )
        {
            if ( this._connectionSettings != null )
                this._connectionSettings.ProviderName = this.ProviderName;
        }
        /// <summary>
        /// Tries to load the settings from the underlying config.
        /// </summary>
        /// <returns></returns>
        private bool TryLoadSettingsFromConfig( )
        {
            ConnectionSettings connectionSettings;
            var driverSettings = this._driverSettings;
            if ( driverSettings == null || null == ( connectionSettings = driverSettings.Connection )
                 || this._connectionInfo == null )
                return false;

            var connectionSettingName = connectionSettings.Name;
            IConnectionSettingsViewModel connectionSetting = null;

            this.SetProviderName( this._connectionInfo.DatabaseInfo.Provider, autoUpdateConnectionSettings: false );

            if ( !String.IsNullOrWhiteSpace( connectionSettingName ) )
                connectionSetting = this._allConnectionSettings
                                        .FirstOrDefault( e => e.Name == connectionSettingName );

            if ( connectionSetting == null ) // auto-select first
                connectionSetting = this.AvailableConnectionSettings
                                        .FirstOrDefault( );

            this.ConnectionSettings = connectionSetting;
            this.SyncProviderNameToConnectionSettings( );
            this.Invalidate( );

            return true;
        }
        /// <summary>
        /// Tries to load the settings for the current connection settings from the underlying config.
        /// </summary>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        private bool TryLoadSettingsForConnectionSettingsFromConfig( )
        {
            var connectionSettings = this._connectionSettings;
            if ( connectionSettings == null )
                return false;

            var connectionInfo = this._connectionInfo;
            var driverSettings = this._driverSettings;
            if ( connectionInfo == null || driverSettings == null )
                return false;

            return connectionSettings.TryLoadSettingsFrom( connectionInfo, driverSettings );
        }
        /// <summary>
        /// Updates the available connection settings.
        /// </summary>
        private void UpdateAvailableConnectionSettings( )
        {
            this.AvailableConnectionSettings = String.IsNullOrWhiteSpace( this._providerName )
                                                    ? null
                                                    : this._allConnectionSettings
                                                          .OrderBy( c => c.Name )
                                                          .Where( s => s.IsProviderSupported( this._providerName ) )
                                                          .ToArray( );

        }
        /// <summary>
        /// Updates the connection settings for the current provider name.
        /// </summary>
        private void UpdateConnectionSettingsForProviderName( )
        {
            var providerName = this._providerName;
            var connectionSettings = this._connectionSettings;
            if ( connectionSettings != null
                    && !connectionSettings.IsProviderSupported( providerName ) )
                connectionSettings = null;

            if ( connectionSettings == null ) // auto-select first
                connectionSettings = this.AvailableConnectionSettings
                                         .FirstOrDefault( );

            this.ConnectionSettings = connectionSettings;
            this.SyncProviderNameToConnectionSettings( );
        }

        #endregion

        #endregion

        #region Protected

        #region - Functions

        /// <summary>
        /// Called when the assigned connection settings have changed.
        /// </summary>
        /// <param name="newSettings">The new settings.</param>
        /// <param name="oldSettings">The old settings.</param>
        protected virtual void OnConnectionSettingsChanged( IConnectionSettingsViewModel newSettings, IConnectionSettingsViewModel oldSettings )
        {
            this.TryLoadSettingsForConnectionSettingsFromConfig( );
            this.Invalidate( );
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets a value indicating whether the default constructor is used for the initialization of the context.
        /// </summary>
        /// <value>
        /// <c>true</c> if the default constructor is used; otherwise, <c>false</c>.
        /// </value>
        protected bool DefaultConstructorIsUsed
        {
            get
            {
                return this.Owner != null && this.Owner.BasicSettings != null && this.Owner.BasicSettings.UseDefaultConstructor;
            }
        }

        #endregion

        #endregion

        #region Public

        #region - Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ConnectionSettingsHostViewModel" /> class.
        /// </summary>
        /// <param name="backendService">The backend service.</param>
        /// <param name="availableConnectionSettings">The available connection settings.</param>
        /// <param name="validator">The validator.</param>
        public ConnectionSettingsHostViewModel( IEnumerable<IConnectionSettingsViewModel> availableConnectionSettings, IValidator<ConnectionSettingsHostViewModel> validator )
            : base( validator )
        {
            Guard.ArgumentNotNull( availableConnectionSettings, "availableConnectionSettings" );
            this._allConnectionSettings = availableConnectionSettings.ToArray( );
            this._availableConnectionSettings = new IConnectionSettingsViewModel[0];

            // TODO: check if injectable by NInject.
            foreach ( var setting in this._allConnectionSettings )
                setting.Host = this;
        }

        #endregion

        #region - Properties

        /// <summary>
        /// Gets the available connection settings.
        /// </summary>
        /// <value>
        /// The available connection settings.
        /// </value>
        public IConnectionSettingsViewModel[] AvailableConnectionSettings
        {
            get
            {
                return this._availableConnectionSettings;
            }
            private set
            {
                if ( this._availableConnectionSettings != value )
                {
                    this._availableConnectionSettings = value ?? new IConnectionSettingsViewModel[0];

                    this.OnPropertyChanged( ( ) => this.AvailableConnectionSettings );
                    this.OnPropertyChanged( ( ) => this.HasAvailableConnectionSettings );
                }
            }
        }
        /// <summary>
        /// Gets the connection settings.
        /// </summary>
        /// <value>
        /// The connection settings.
        /// </value>
        public IConnectionSettingsViewModel ConnectionSettings
        {
            get
            {
                return this._connectionSettings;
            }
            set
            {
                if ( this._connectionSettings != value )
                {
                    var oldSettings = this._connectionSettings;
                    this._connectionSettings = value;

                    if ( oldSettings != null )
                        oldSettings.IsActive = false;

                    this.OnConnectionSettingsChanged( value, oldSettings );

                    if ( value != null )
                        value.IsActive = true;

                    this.OnPropertyChanged( ( ) => this.ConnectionSettings );

                    this.SyncProviderNameToConnectionSettings( );
                    this.Invalidate( );
                }
            }
        }
        /// <summary>
        /// Gets a value indicating whether the connection settings are required.
        /// </summary>
        /// <value>
        /// <c>true</c> if the connection settings are required; otherwise, <c>false</c>.
        /// </value>
        public bool ConnectionSettingsAreRequired
        {
            get
            {
                return !this.DefaultConstructorIsUsed;
            }
        }
        /// <summary>
        /// Gets a value indicating whether this instance has available connection settings.
        /// </summary>
        /// <value>
        /// <c>true</c> if this instance has available connection settings; otherwise, <c>false</c>.
        /// </value>
        public bool HasAvailableConnectionSettings
        {
            get
            {
                return this._availableConnectionSettings != null && this._availableConnectionSettings.Length > 0;
            }
        }
        /// <summary>
        /// Gets or sets the name of the provider.
        /// </summary>
        /// <value>
        /// The name of the provider.
        /// </value>
        public string ProviderName
        {
            get
            {
                return this._providerName;
            }
            set
            {
                this.SetProviderName( value, autoUpdateConnectionSettings: true );
            }
        }

        #endregion

        #endregion

        #region DriverSubSettingsViewModelBase<ConnectionSettingsHostViewModel> Members

        #region - Functions

        /// <summary>
        /// Called when the assigned owner has changed.
        /// </summary>
        /// <param name="newOwner">The new owner.</param>
        /// <param name="oldOwner">The old owner.</param>
        protected override void OnOwnerChanged( DriverSettingsViewModel newOwner, DriverSettingsViewModel oldOwner )
        {
            this.TryLoadSettingsFromConfig( );
            this.TryLoadSettingsForConnectionSettingsFromConfig( );
        }

        #endregion

        #endregion

        #region IPersistableSettingsViewModel<DriverSettings> Members

        /// <summary>
        /// Tries to load the view model's settings from the specified sources.
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="source">The source.</param>
        /// <returns>
        ///   <c>true</c> on success; otherwise, <c>false</c>.
        /// </returns>
        public bool TryLoadSettingsFrom( IConnectionInfo connectionInfo, DriverSettings source )
        {
            this._connectionInfo = connectionInfo;
            this._driverSettings = source;
            
            var backendLoaded = this.TryLoadSettingsFromConfig( );
            var connectionSettingsApplied = this.TryLoadSettingsForConnectionSettingsFromConfig( );

            return backendLoaded && connectionSettingsApplied;
        }
        /// <summary>
        /// Writes the view model's settings to the specified targets.
        /// </summary>
        /// <param name="connectionInfo">The connection info.</param>
        /// <param name="target">The target.</param>
        public void WriteSettingsTo( IConnectionInfo connectionInfo, DriverSettings target )
        {
            Guard.ArgumentNotNull( connectionInfo, "connectionInfo" );
            Guard.ArgumentNotNull( target, "target" );

            var connectionSettings = this._connectionSettings;
            if ( connectionSettings == null || !this.ConnectionSettingsAreRequired )
                target.Connection = null;
            else
                connectionSettings.WriteSettingsTo( connectionInfo, target );
        }

        #endregion
    }
}
